// addrspace.cc 
//	Routines to manage address spaces (executing user programs).
//
//	In order to run a user program, you must:
//
//	1. link with the -N -T 0 option 
//	2. run coff2noff to convert the object file to Nachos format
//		(Nachos object code format is essentially just a simpler
//		version of the UNIX executable object code format)
//	3. load the NOFF file into the Nachos file system
//		(if you haven't implemented the file system yet, you
//		don't need to do this last step)
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "system.h"
#include "addrspace.h"
#include "noff.h"
#include "table.h"
#include "synch.h"

extern "C" { int bzero(char *, int); };

Table::Table(int s) : map(s), table(0), lock(0), size(s) {
    table = new void *[size];
    lock = new Lock("TableLock");
}

Table::~Table() 
{
    if (table) 
    {
        delete table;
        table = 0;
    }
    
    if (lock) 
    {
        delete lock;
        lock = 0;
    }
}

void *Table::Get(int i) 
{
    // Return the element associated with the given if, or 0 if
    // there is none.

    return (i >=0 && i < size && map.Test(i)) ? table[i] : 0;
}

int Table::Put(void *f) 
{
    // Put the element in the table and return the slot it used.  Use a
    // lock so 2 files don't get the same space.
    int i;	// to find the next slot

    lock->Acquire();
    i = map.Find();
    lock->Release();
    if ( i != -1)
        table[i] = f;
    return i;
}

void *Table::Remove(int i) 
{
    // Remove the element associated with identifier i from the table,
    // and return it.

    void *f =0;

    if ( i >= 0 && i < size ) 
    {
        lock->Acquire();
        if ( map.Test(i) ) 
        {
            map.Clear(i);
            f = table[i];
            table[i] = 0;
        }
        lock->Release();
    }
    return f;
}

//----------------------------------------------------------------------
// SwapHeader
// 	Do little endian to big endian conversion on the bytes in the 
//	object file header, in case the file was generated on a little
//	endian machine, and we're now running on a big endian machine.
//----------------------------------------------------------------------

static void 
SwapHeader (NoffHeader *noffH)
{
	noffH->noffMagic = WordToHost(noffH->noffMagic);
	noffH->code.size = WordToHost(noffH->code.size);
	noffH->code.virtualAddr = WordToHost(noffH->code.virtualAddr);
	noffH->code.inFileAddr = WordToHost(noffH->code.inFileAddr);
	noffH->initData.size = WordToHost(noffH->initData.size);
	noffH->initData.virtualAddr = WordToHost(noffH->initData.virtualAddr);
	noffH->initData.inFileAddr = WordToHost(noffH->initData.inFileAddr);
	noffH->uninitData.size = WordToHost(noffH->uninitData.size);
	noffH->uninitData.virtualAddr = WordToHost(noffH->uninitData.virtualAddr);
	noffH->uninitData.inFileAddr = WordToHost(noffH->uninitData.inFileAddr);
}

//----------------------------------------------------------------------
// AddrSpace::AddrSpace
// 	Create an address space to run a user program.
//	Load the program from a file "executable", and set everything
//	up so that we can start executing user instructions.
//
//	Assumes that the object code file is in NOFF format.
//
//	"executable" is the file containing the object code to load into memory
//
//      It's possible to fail to fully construct the address space for
//      several reasons, including being unable to allocate memory,
//      and being unable to read key parts of the executable.
//      Incompletely consretucted address spaces have the member
//      constructed set to false.
//----------------------------------------------------------------------

AddrSpace::AddrSpace(OpenFile *executable) : fileTable(MaxOpenFiles) 
{
    NoffHeader noffH;
    unsigned int i, size;
	int memOffset;

    // Don't allocate the input or output to disk files
    fileTable.Put(0);
    fileTable.Put(0);
	
    executable->ReadAt((char *)&noffH, sizeof(noffH), 0);
    if ((noffH.noffMagic != NOFFMAGIC) && (WordToHost(noffH.noffMagic) == NOFFMAGIC))
    	SwapHeader(&noffH);
    ASSERT(noffH.noffMagic == NOFFMAGIC);

    size = noffH.code.size + noffH.initData.size + noffH.uninitData.size ;
    numPages = divRoundUp(size, PageSize) + divRoundUp(UserStackSize,PageSize);
                                                // we need to increase the size
												// to leave room for the stack
    size = numPages * PageSize;

    ASSERT(numPages <= NumPhysPages);		// check we're not trying
											// to run anything too big --
											// at least until we have
											// virtual memory

    DEBUG('a', "Initializing address space, num pages %d, size %d\n", numPages, size);
	
	mainMemoryLock->Acquire();
	
    // first, set up the translation 
    pageTable = new TranslationEntry[numPages];
    for (i = 0; i < numPages; i++) 
	{
		pageTable[i].virtualPage = i;	// for now, virtual page # = phys page #
		pageTable[i].physicalPage = memoryMap->Find();
		pageTable[i].valid = TRUE;
		pageTable[i].use = FALSE;
		pageTable[i].dirty = FALSE;
		pageTable[i].readOnly = FALSE;  // if the code segment was entirely on 
										// a separate page, we could set its 
										// pages to be read-only
		
		// Clear page (zero it out)
		memOffset = pageTable[i].physicalPage * PageSize;
		bzero(machine->mainMemory + memOffset, PageSize);
    }
    
	mainMemoryLock->Release();
	
	RestoreState();

	// then, copy in the code and data segments into memory
    
	if (noffH.code.size > 0) 
    {
		mainMemoryLock->Acquire();
		
		int physAddr, currentPage, currentPageSize, codeRead = 0;
		int numDataPages = divRoundUp(noffH.code.size, PageSize);
		ExceptionType exception = (ExceptionType)0;
		
		for(i = 0; i < numDataPages; i++)
		{
			if(i == 0)
				currentPageSize = ((noffH.code.virtualAddr % PageSize) > 0) ? (PageSize - (noffH.code.virtualAddr % PageSize)) : PageSize;
			currentPageSize = ((codeRead + currentPageSize) > noffH.code.size) ? (noffH.code.size - codeRead) : currentPageSize;
			
			exception = machine->Translate(noffH.code.virtualAddr + codeRead, &physAddr, 0, true);
			if(exception == NoException)
			{
				DEBUG('a', "Initializing code page %d of %d, at virtual addr 0x%x (phys addr: 0x%x)\n", i+1, numDataPages, noffH.code.virtualAddr + i, physAddr);
				executable->ReadAt(&(machine->mainMemory[physAddr]), currentPageSize, noffH.code.inFileAddr + codeRead);
				codeRead = codeRead + currentPageSize;
			}
			else
			{
				DEBUG('a', "Failed to initialize code page %d, at virtual address 0x%x\n", i+1, noffH.code.virtualAddr + i);
				machine->RaiseException(exception, noffH.code.virtualAddr + i);
			}
		}
		
		mainMemoryLock->Release();
    }
	
    if (noffH.initData.size > 0) 
    {
		mainMemoryLock->Acquire();
		
		int physAddr, currentPage, currentPageSize, dataRead = 0;
		int numDataPages = divRoundUp(noffH.initData.size, PageSize);
		ExceptionType exception = (ExceptionType)0;

		for(i = 0; i < numDataPages; i++)
		{
			if(i == 0)
				currentPageSize = ((noffH.initData.virtualAddr % PageSize) > 0) ? (PageSize - (noffH.initData.virtualAddr % PageSize)) : PageSize;
			currentPageSize = ((dataRead + currentPageSize) > noffH.initData.size) ? (noffH.initData.size - dataRead) : currentPageSize;
			
			exception = machine->Translate(noffH.initData.virtualAddr + dataRead, &physAddr, 0, true);
			if(exception == NoException)
			{
				DEBUG('a', "Initializing data page %d of %d, at virtual addr 0x%x (phys addr: 0x%x)\n", i+1, numDataPages, noffH.initData.virtualAddr + i, physAddr);
				executable->ReadAt(&(machine->mainMemory[physAddr]), currentPageSize, noffH.initData.inFileAddr + dataRead);
				dataRead = dataRead + currentPageSize;
			}
			else
			{
				DEBUG('a', "Failed to initialize data page %d, at virtual address 0x%x\n", i+1, noffH.initData.virtualAddr + i);
				machine->RaiseException(exception, noffH.initData.virtualAddr + i);
			}
		}
		
		mainMemoryLock->Release();
    }
}

//----------------------------------------------------------------------
// AddrSpace::~AddrSpace
//
// 	Dealloate an address space.  release pages, page tables, files
// 	and file tables
//----------------------------------------------------------------------

AddrSpace::~AddrSpace()
{
	mainMemoryLock->Acquire();
	for(unsigned int i = 0; i < numPages; i++)
	{
		memoryMap->Clear(pageTable[i].physicalPage);
	}
	mainMemoryLock->Release();
    delete pageTable;
}

//----------------------------------------------------------------------
// AddrSpace::InitRegisters
// 	Set the initial values for the user-level register set.
//
// 	We write these directly into the "machine" registers, so
//	that we can immediately jump to user code.  Note that these
//	will be saved/restored into the currentThread->userRegisters
//	when this thread is context switched out.
//----------------------------------------------------------------------

void
AddrSpace::InitRegisters()
{
    int i;

    for (i = 0; i < NumTotalRegs; i++)
	machine->WriteRegister(i, 0);

    // Initial program counter -- must be location of "Start"
    machine->WriteRegister(PCReg, 0);	

    // Need to also tell MIPS where next instruction is, because
    // of branch delay possibility
    machine->WriteRegister(NextPCReg, 4);

   // Set the stack register to the end of the address space, where we
   // allocated the stack; but subtract off a bit, to make sure we don't
   // accidentally reference off the end!
    machine->WriteRegister(StackReg, numPages * PageSize - 16);
    DEBUG('a', "Initializing stack register to %x\n", numPages * PageSize - 16);
}

//----------------------------------------------------------------------
// AddrSpace::SaveState
// 	On a context switch, save any machine state, specific
//	to this address space, that needs saving.
//
//	For now, nothing!
//----------------------------------------------------------------------

void AddrSpace::SaveState() 
{}

//----------------------------------------------------------------------
// AddrSpace::RestoreState
// 	On a context switch, restore the machine state so that
//	this address space can run.
//
//      For now, tell the machine where to find the page table.
//----------------------------------------------------------------------

void AddrSpace::RestoreState() 
{
    machine->pageTable = pageTable;
    machine->pageTableSize = numPages;
}

//----------------------------------------------------------------------
// AddrSpace::CopyTranslationTable
// 	Creates a new translation table which is a duplicate of
//	the one passed into this function.
//
//  Returns a pointer to the new translation table.
//----------------------------------------------------------------------

int AddrSpace::CreateThreadStack() 
{
	int memOffset;
    int stackSize = divRoundUp(UserStackSize, PageSize);
	
	// Create new page table (which is one stack size larger)
	TranslationEntry* newPageTable = new TranslationEntry[numPages + stackSize];
	
	// Copy existing page table to new one
	for (int i = 0; i < numPages; i++)
	{
		newPageTable[i].virtualPage = pageTable[i].virtualPage;
		newPageTable[i].physicalPage = pageTable[i].physicalPage;
		newPageTable[i].valid = pageTable[i].valid;
		newPageTable[i].use = pageTable[i].use;
		newPageTable[i].dirty = pageTable[i].dirty;
		newPageTable[i].readOnly = pageTable[i].readOnly;
	}
	
	mainMemoryLock->Acquire();
	
	// Add new pages to page table
	for(unsigned int i = numPages; i < numPages + stackSize; i++)
	{
		newPageTable[i].virtualPage = i;	// for now, virtual page # = phys page #
		newPageTable[i].physicalPage = memoryMap->Find();
		newPageTable[i].valid = TRUE;
		newPageTable[i].use = FALSE;
		newPageTable[i].dirty = FALSE;
		newPageTable[i].readOnly = FALSE;  // if the code segment was entirely on 
										   // a separate page, we could set its 
										   // pages to be read-only
		// Clear page (zero it out)
		memOffset = pageTable[i].physicalPage * PageSize;
		bzero(machine->mainMemory + memOffset, PageSize);
	}
	
	mainMemoryLock->Release();
	
	// clean up
	delete[] pageTable;
	pageTable = newPageTable;
	machine->pageTable = newPageTable;
	numPages = numPages + stackSize;
	machine->pageTableSize = numPages;
	
	// Return stack pointer
	return pageTable[numPages - 1].virtualPage + PageSize - 1;
}

void AddrSpace::DeleteThreadStack(int vAddr)
{
	int top = ((vAddr - UserStackSize) / PageSize);
	int bottom = (vAddr / PageSize);
	int physPageNum;
	
	mainMemoryLock->Acquire();
	
	for (int i = top; i < bottom; i++) 
	{
		physPageNum = pageTable[i].physicalPage;
		
		if(pageTable[i].valid)
		{
			bzero(machine->mainMemory + (physPageNum * PageSize), PageSize);
			memoryMap->Clear(physPageNum);
		}
	}
				  
	mainMemoryLock->Release();
}